查看原文
其他

0803漏洞利用分析

Sebastianasd 看雪学院 2019-09-17

作者对0803漏洞的利用工具进行改进使其运用在windows server2012系统环境中,已成功。这是对之前的分析做的小结。本文分享了有关该漏洞的触发原理及利用方法。


简要说明

cve-2019-0803

漏洞类型

UAF

时限

2019年4月公布的补丁

漏洞位置、应用环境

全覆盖


测试环境及场景:


Sever2008r2

Win7_64

server2012r2

2008r2_sp1

Win7_64_sp1


分析该漏洞的目的是想改进EXP使其能运用至server2012系统。最后一小节总结了需更修改的地方。


漏洞原理


DDE通信时,client向server请求数据后,sever返回数据时,首先会调用用户层函数_clientCopyDDEIn1(usr32.dll中),该函数是将一些数据填充至tagINTDDEINFO结构,然后再调用hmgSetOwner函数(win32k.sys中),该函数会将tagINTDDEINFO中的成员hIndirect(GDI句柄)返回给client。

因此,利用思路是,先新建一个A进程并创建得到一个对象句柄X,然后修改用户层函数_clientCopyDDEIn1,将tagINTDDEINFO的成员hIndirect修改为X,这样返回给client的对象就是一个外部对象X。


Client结束后会释放X,然后再用事先构造好的Y占用该内存区域,那么A进程可以用控制X的函数(此处为SetDIBColorTable函数)修改Y。


UAF漏洞的利用,雷同double-free漏洞(之前分析的cve-2018-8639),这里也运用了简单的fengshui布局。此外,采用了任意地址读写技术,利用了tagWND结构中cbWNDExtra成员、strName成员、成员spwndParent



相关技术



DDE通信        



UAF与double-free


double-free

图2


UAF

图3


由以上两图对比可知,UAF就是double-free的前两步,根据之前cve-2018-8639对double-free的原理分析可知,double-free中的对象B要满足:有相关的函数可以更改B对象的内容。


那么对于UAF中,存在漏洞的对象A需要满足该条件(本漏洞中就是SetDIBColorTable函数)。其实两个漏洞类型的利用原理是一致的。



UAF漏洞的利用及任意内存地址读写


对照图3及”漏洞原理”一节,存在漏洞的对象A是DIB(Device-independent bitmap)类型,然后在client释放A后,用B(构造好的HACCEL)占用A,最后用SetDIBColorTable函数(控制DIB对象)更改B的值。


关于对象B

1、B的大小与A相同

2、SetDIBColorTable函数是如何修改A的,从而构造B

3、如何通过一次修改B,可以实现任意读写内存地址


问题1:

通过读代码[2]可知A就是bitmap对象,其大小0x350(win7/server2008),在server2012系统下为0x370。

另外,构建A的过程并不是简单的createbitmap(大概与漏洞的触发有关,不是很懂),而是通过建一个"#32768"类的窗口,获得设备上下文的句柄hdc,最后通过hdc得到”OBJ_BITMAP”类型的对象的句柄即对象A。(PS:用gdiview等GDI对象查看的小工具无法看到A)。


对象B选取 accelerator table对象,一个ACCEL结构的数组。通过CreateAcceleratorTable函数新建对象,参数2确定了对象的大小。当为132时,对象大小为0x350,当为138时,对象大小为0x370。 


问题2:

关于SetDIBColorTable(hdc,i, n, const RGBQUAD *prgbq)函数如何修改对象DIB,则需逆向该函数(参见“相关函数及术语”),同时还要了解_PALETTE64对象。


该函数是对hdc中的DIB对象(实际就是_PALETTE64对象)的i开始的n个PALETTENTRY结构进行写。对应到内存中具体的对象的操作就是对_PALETTE64对象的apalColors[]数组进行写操作。


需要说明的是对象的成员pFirstColor就指向该数组,因此构造一个_PALETTE64对象,使其成员pFirstColor不是指向位图对象的数组apalColors[],而是一个我们想更改的内存位置,那么再调用SetDIBColorTable函数时,令i为0,n为1,就更改了我们指定的内存。


(这一处没想通,需要好好分析下SetDIBColorTable的具体过程)


根据code,构造的ACELL数组是在第[15]号ACELL放入构造的_PALETTE64对象地址,然后构造的_PALETTE64对象的成员pFirstColor(偏移0x80处)放入待更改的内存地址xxxx。这样调用SetDIBColorTable函数后就能更改xxxx处的值。


问题3:

如何通过更改一个值,达到任意读写内存的目的。此处利用了tagWND结构中cbWNDExtra成员、strName成员、成员spwndParent。


创建两个icon类的窗口分别为hwnd1hwnd2,然后设置xxxxhwnd1的成员cbWNDExtra(偏移0xe8)的地址,通过UAF漏洞更改一次cbWNDExtra值,使窗口hwnd1可以越界写hwnd2(用函数SetWindowLong),然后越界写hwnd2的StrName中的buffer


hwnd2就可以调用函数SetWindowText实现任意内存地址写。任意内存地址读:先通过越界写更改hwnd2的成员spwndParent,然后hwnd2通过调用GetAncestor函数实现任意内存地址读。

 


fengshui布局

为了使A释放后,B能顺利占据其内存区域,对内核池进行布局(括号内表示server20012系统下),布局使用的对象为ACCEL。


1、因为A的大小为0x350(0x370),分配200个由132(138)个ACCEL组成的0x350(0x370)大小的ACCEL数组,占用内存空间中分散的0x350(0x370)大小的内存空洞。

2、分配1000个由533(527)个ACCEL组成的0xCB0(0xC90)大小的ACCEL数组,使新分配的内存页面留出0x350(0x370)的空间,方便A能分配至此.(0x350=0x1000-0xc90)。

3、分配400个bitmap对象。

4、创建对象A,占用之前构造的内存空洞。

5、再分配400个bitmap对象。

6、释放第三步分配的前300个bitmap对象。


释放A后

1、释放400个从第300到第700的bitmap。

2、分配2000个构建好的ACCEL数组,即用B占A释放后留下的内存空间。

 


用户层函数截持

__ClientCopyDDEIn1是用户层函数




截持过程

目的:在call _ClientCopyDDEIn1指令后开始截持。


1、截持跳板


UCHAR HijackJmpShellcode[] = {

0x50, //push rax

0x48, 0xB8, //mov rax, 0xcccccccc

0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,

0xFF, 0xE0 //jmp rax

};//13个字节


2、跳转后运行的程序


HijackTrampoFunc PROC

pushr8

lea rax,[rsp+50h]

mov r8,qword ptr g_BitMapAddr

mov qword ptr [rax+30h],r8

mov r8,qword ptr [rax+20h]

mov byte ptr [r8+2],2

pop r8

pop rax

xor r8d,r8d

mov eax,eax

lea rcx,[rsp+20h]

lea edx,[r8+18h]

jmp qword ptr ContinueAddr

HijackTrampoFunc ENDP

红色:关键的替换返回对象的代码

蓝色:堆栈平衡

绿色:原函数中被HijackJmpShellcode[](13个字节)覆盖的指令

黄色:跳转到覆盖后的指令,继续执行,ContinueAddr中存放mov[rsp+98h+var_78], r11,指令的地址

说明:以上为win7中的__ClientCopyDDEIn1函数,win2012中有细微差别。


3、跳转

HijackJmpShellcode[3-10]8个字节放入HijackTrampoFunc程序的地址。



函数逆向

截持后运行的红色代码是关键,这块代码的编写首先需要了解__ClientCopyDDEIn1的功能,然后要知道tagINTDDEINFO的结构信息(主要是成员hIndirect的偏移),最后要知道在这个函数中tagINTDDEINFO在内存中的哪个位置,最终才能将其成员hIndirect替换为我们的DIB对象的句柄。


_ClientCopyDDEIn1的功能


_ClientCopyDDEIn1(void*p1,structtagINFDDEINFO*p2)


把p1写入p2,p1是以外层函数__ClientCopyDDEIn1的参数(通过rcx寄存器传入)为内存地址中的值。P2是栈空间中的地址([rsp+40h][rbp-58h]),所以该函数就是复制p1处的数据填充至栈空间(以[rsp+40h]为首地址)。


tagINTDDEINFO的结构信息


根据code中的红色代码,猜测偏移40h为成员hIndirect,之后又更改了偏移30处的指针指向的第2字节的值为2(不知道是个啥)。


说明:好在这一点win server 2008win server 2012没有不同,不然要到哪去分析tagINTDDEINFO的结构信息!!!!


token替换[4]

1、获得当前进程的_EPROCESS结构

2、通过_EPROCESS结构中的成员ActiveProcessLinks及成员UniqueProcessId(pid)遍历搜索出system进程(pid为4)

3、保存system进程的token值

4、覆盖当前进程的token


图1


说明:

1、对于第1步,之前算得了两个icon类窗口的地址即pwndIcon1pwndIcon2,用其中的pwndIcon1即得到图中的tagWND的地址,通过图中的关系可以得到当前进程的_EPROCESS结构。


2、图中各结构中成员的偏移值在不同的系统中不相同,在server2008与server2012的具体值可通过windbg获得,在code中有标出。



3、覆盖token时又卡了。写内存时BOSD,一开始以为server12引进了什么安全机制不允许token替换,后来发现是写操作用的函数SetWindowText中的一些注意事项。参见“相关函数及术语”的“SetWindowText”。

 


相关函数及术语

Bitmap和DIB :https://www.xuebuyuan.com/1492074.html




SetWindowText

BOOL SetWindowText(

HWND hwnd,

LPCTSTR lpString

)
;//hwnd


窗口的句柄,lpString指向一个空结束的字符串的指针

 

具体操作:

把窗口tagWND结构中的成员StrName中的Buffer成员指向的内存处写入lpString字符串。tagWND结构参考图1:


应用于提权中的写操作:更改Buffer值(即写地址),调用SetWindowText,对内存中任意地址进行写操作,但在运用中遇到一些问题:


1、lpString是以空结束的字符串,因此再写内存时,会多覆盖1字节的0,测试时导致BOSD(因为对当前进程的token值后的一字节也进行了修改,修改为0),因此,保存token后的若干字节(这些字节是以0结尾的),然后再覆盖token之后,对这些值再重写一下。


unsignedlonglong tmp = ReadPtrFromKernelMemory(MyEPROCESSAddr + OFFSET_SECTOKEN_WIN7+8);



wchar_t tmp2[5] = { 0x00 };

tmp2[3] = (tmp >> 48) & 0xFFFF;

tmp2[2] = (tmp >> 32) & 0xFFFF;

tmp2[1] = (tmp >> 16) & 0xFFFF;

tmp2[0] = (tmp >> 0) & 0xFFFF;

....

....

WriteKernelAddress(MyEPROCESSAddr + OFFSET_SECTOKEN_WIN7 + 8, tmp2);

2、当lpString字符串长度大于成员StrName的MaximumLength(该值在第一次调用SetWindowText时确定)时,会在Buffer指针指向的内存处写入任意值,最终导致BOSD(因为对当前进程的token值附近进行了修改)。


因此在第一次调用SetWindowText时(为了之后测试是否能写成功),尽量使StrName的MaximumLength大一些。 


3、tagWND结构在server 2012系统中已经不公开。因此也花了些时间确定其成员StrName,cbwndExtra的偏移量及tagWND结构的长度。


 


SetDIBColorTable函数

UINT SetDIBColorTable(

HDC hdc,

UINT iStart,

UINT cEntries,

const RGBQUAD *prgbq

);


4 个参数


A. Gdi32.dll

SetDIBColorTable()->ZwGdiDoPalette()->系统调用号

 

图1



B. User32.dll

存在ZwGdiDoPalette()


C. Win32k.sys

NtGdiDoPalette()->GreSetDIBColorTable()->XEPALOBJ::vCopy_rgbquad

参数如图1中的ZwGdiDoPalette():


因此调用GreSetDIBColorTable()参数值同SetDIBColorTable()。


Surface_object = *(struct SURFACE **)(HDC_Copy2 + 0x1F8);

// surface object pointer from HDC+0x1F8[3]

....

v10 = *((_QWORD *)Surface_object + 15);

Obj_15 = v10;

//Obj_15就是PALETTE64对象,这里偏移了15

....

XEPALOBJ::vCopy_rgbquad((XEPALOBJ *)&Obj_15, rgb, start, numm);

//开始覆值

汇编

//r10是change_offset即PALETTEENTRY rdx是rgb即RGBQUAD

//就是对PALETTEENTRY覆值RGBQUAD

两个对象的结构


typedef struct tagRGBQUAD {

BYTE rgbBlue;

BYTE rgbGreen;

BYTE rgbRed;

BYTE rgbReserved;

} RGBQUAD;


typedef struct tagPALETTEENTRY {

BYTE peRed;

BYTE peGreen;

BYTE peBlue;

BYTE peFlags;

} PALETTEENTRY, *PPALETTEENTRY, *LPPALETTEENTRY;



server2008与server2012


1、由于_ClientCopyDDEIn1函数在不同系统中实现的细微差异,因此在截持时要做相应的修改,包括HijackTrampoFunc中绿色部分及黄色部分的继续跳转的地址ContinueAddr的值。

2、Fengshui布局中对象大小的设置。

3、Token获取时,内核结构在不同系统中,各成员的偏移位置的不同。

4、任意内存地址读写时,tagWND结构中关键成员偏移位置的确定及tagWND的长度。

5、内核函数的调用(Nt**)转换为调用用户层函数。因为不同系统中内核函数的调用号不同,代码中(win7场景)对内核函数的调用,用在win2012系统中不能成功,好在都有对应的用户层函数,直接替换就好。




- End -



看雪ID:Sebastianasd 

https://bbs.pediy.com/user-816842.htm  



本文由看雪论坛 Sebastianasd 原创

转载请注明来自看雪社区








京华结交尽奇士,意气相期矜豪纵。今夏与君相约看雪,把酒言欢,共话安全技术江湖梦。


10大议题正式公布!第三届看雪安全开发者峰会重磅来袭!



往期热门回顾

1、C/C++反混淆方法

2、APICloud解密本地资源到逆向APP算法到通用资源解密

3、我的微信数据监控研究发展过程

4、网络沙场大点兵!2019 WCTF世界黑客大师赛鸣锣开战

5、如何实现 Https拦截进行 非常规“抓包”




      ↙点击下方“阅读原文”,查看更多干货

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存